#include <kew/c-vm.h>
#include <glib.h>

/*
 * All message-sending functions, including several convenience functions.
 * There is no longer a hidden variant.
 */

gboolean kew_method_check(KewObject *receiver, KewSelector selector)
{
    return receiver->type->dispatch_map[selector].method != kew_not_understood_method;
}

static inline guint count_values(KewObject **buffer, guint size, guint multi)
{
    guint total = 0;
    
    for (i = 0; i < size; ++i) {
        if (multi & KEW_CALL_SHAPE_MULTI(i)) {
            KewListObject *list = kew_list_val(buffer[i]);
            total += g_ptr_array_size(list->array);
        } else
            ++total;
    }
    return total;}

static inline void remarshall(KewObject **in_buffer,
                              guint in_size,
                              guint in_multi,
                              KewObject **out_buffer,
                              guint out_size,
                              guint out_multi)
{
    /* there's a clever way of doing this that avoids the intermediate copy,
     * but it's too clever for me */
    guint current = 0;
    guint buffer_size = count_values(in_buffer, in_size, in_multi);
    KewObject **buffer = g_newa(KewObject *, buffer_size);
    guint buffer_index = 0;

    /* copy "in" to intermediate buffer */
    for (i = 0; i < in_size; ++i) {
        
        if (in_multi & KEW_CALL_SHAPE_MULTI(i)) {
            
            KewListObject *list = kew_list_val(in_buffer[i]);
            guint size_of_list = g_ptr_array_size(list->array);
            
            for (j = 0; j < size_of_list; ++j) {
                buffer[buffer_index] = g_ptr_array_get_index(list->array, j);
                ++buffer_index;
            }
        } else {
            buffer[buffer_index] = in_buffer[i];
            ++buffer_index;
        }
    }

    buffer_index = 0;
    
    /* copy intermediate buffer to "out" */
    for (i = 0; i < out_size; ++i) {
        
        if (out_multi & KEW_CALL_SHAPE_MULTI(i)) {

            gint number_of_values = buffer_size - out_size;
            
            if (number_of_values < 0)
                g_error("FIXME: RemarshallException should be raised here");
            
            out_buffer[i] = kew_wrap_list(g_ptr_array_copy_from_array(buffer+buffer_index, number_of_values));
            
        } else {
            
            if (buffer_size - buffer_index <= 1)
                g_error("FIXME: RemarshallException should be raised here");
                
            out_buffer[i] = buffer[buffer_index];
            ++buffer_index;
        } 
    }
}

void kew_send_full(KewObject *receiver,
                   KewSelector selector,
                   KewCallShape caller_shape,
                   KewObject **caller_args,
                   KewObject **caller_results,
                   KewSourcePoint caller_source_point)
{
    KewCallFrame frame;
    KewDispatchMap *dispatch_map;
    KewCallShape callee_shape;
    KewThreadContext *current_context = (*kew__thread_system->current_context)();

    /* create a new call frame */
    frame.source_point = caller_source_point;
    frame.security_context = caller_security_context;
    frame.receiver = receiver;
    frame.args = caller_args;
    frame.next = current_context->top_call_frame;
    
    current_context->top_call_frame = &frame;
    
    /* look up the selector in the receiver's dispatch map */
    dispatch_map = &receiver->type->dispatch_map[selector];
    callee_shape = dispatch_map->callee_shape;
    
    /* try a "fast" send (no remarshalling) */
    if (caller_shape == callee_shape)
        (*dispatch_map->method)(env, receiver, caller_args, caller_results);

    /* otherwise, do a "slow" send (using remarshalling) */
    else {
        KewObject **args;
        KewObject **results;
        
        /* try to avoid remarshalling the arguments */
        if (KEW_CALL_SHAPE_ARG_MATCH(caller_shape, callee_shape))
            args = caller_args;            
        else {
            args = g_newa(KewObject *, KEW_CALL_SHAPE_NUMBER_OF_ARGS(callee_shape));
            
            remarshall(caller_args, KEW_CALL_SHAPE_NUMBER_OF_ARGS(caller_shape), KEW_CALL_SHAPE_MULTI_ARG_LIST(caller_shape),
                       args, KEW_CALL_SHAPE_NUMBER_OF_ARGS(callee_shape), KEW_CALL_SHAPE_MULTI_ARG_LIST(callee_shape));
        }

        /* try to avoid remarshalling the results
         * this uses an "extended split" to slightly improve efficiency */
        if (KEW_CALL_SHAPE_RESULT_MATCH(caller_shape, callee_shape)) {
            results = caller_results;
            
            (*dispatch_map->method)(env, receiver, args, results);
        } else {
            results = g_newa(KewObject *, KEW_CALL_SHAPE_NUMBER_OF_RESULTS(callee_shape));
            
            (*dispatch_map->method)(env, receiver, args, results);
            
            remarshall(results, KEW_CALL_SHAPE_NUMBER_OF_RESULTS(callee_shape), KEW_CALL_SHAPE_MULTI_RESULT_LIST(callee_shape),
                       caller_results, KEW_CALL_SHAPE_NUMBER_OF_RESULTS(caller_shape), KEW_CALL_SHAPE_MULTI_RESULT_LIST(caller_shape));
        }
    }
    
    /* peel off the call frame */
    current_context->top_call_frame = frame.next;
}


KEW_EXPORT_METHOD(kew_not_understood_method)
{
    g_error("Message not understood FIXME: this should be an exception");
}
